package com.kookapi.service.impl;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kookapi.asserts.AssertionComponent;
import com.kookapi.asserts.register.AssertionComponentRegistry;
import com.kookapi.asserts.result.AssertionResult;
import com.kookapi.business.domain.ApiDefinition;
import com.kookapi.business.domain.TestScenario;
import com.kookapi.business.domain.TestScenarioData;
import com.kookapi.business.domain.TestStep;
import com.kookapi.business.service.IApiDefinitionService;
import com.kookapi.business.service.ITestScenarioDataService;
import com.kookapi.business.service.ITestScenarioService;
import com.kookapi.business.service.ITestStepService;
import com.kookapi.common.constant.UserConstants;
import com.kookapi.common.exception.ServiceException;
import com.kookapi.dto.TaskRequestDTO;
import com.kookapi.enums.HttpEnum;
import com.kookapi.service.AllureReportService;
import com.kookapi.service.TestCoreService;
import com.kookapi.utils.JsonUtils;
import com.kookapi.utils.SimpleTemplateUtils;
import io.qameta.allure.Allure;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.model.TestResultContainer;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author bairimengchang
 * @version 1.0
 * @project kookapi
 * @description
 * @date 2025/10/8
 */
@Service
@Slf4j
public class TestCoreServiceImpl implements TestCoreService {

    // 测试场景服务
    private final ITestScenarioService testScenarioService;

    // 测试步骤服务
    private final ITestStepService testStepService;

    // 测试参数服务
    private final ITestScenarioDataService testScenarioDataService;

    // 测试接口定义服务
    private final IApiDefinitionService apiDefinitionMapper;

    // 断言组件扫描
    private final AssertionComponentRegistry assertionComponentRegistry;

    // allure测试报告服务
    private final AllureReportService allureReportService;

    public TestCoreServiceImpl(
            ITestScenarioService testScenarioService,
            ITestStepService testStepService,
            ITestScenarioDataService testScenarioDataService,
            IApiDefinitionService apiDefinitionMapper,
            AssertionComponentRegistry assertionComponentRegistry,
            AllureReportService allureReportService
    ) {
        this.testScenarioService = testScenarioService;
        this.testStepService = testStepService;
        this.testScenarioDataService = testScenarioDataService;
        this.apiDefinitionMapper = apiDefinitionMapper;
        this.assertionComponentRegistry = assertionComponentRegistry;
        this.allureReportService = allureReportService;
    }

    @Override
    public boolean executeTestTask(TaskRequestDTO taskRequestDTO) {
        // log.info("任务ID：{}", taskRequestDTO.getTaskId());
        log.info("场景ID：{}", taskRequestDTO.getScenarioId());

        // 取出场景id
        Long scenarioId = taskRequestDTO.getScenarioId();
        // 1. 根据场景id查询场景信息（allure需要）
        TestScenario testScenario = testScenarioService.selectTestScenarioById(scenarioId);
        log.info("场景名称：{}", testScenario.getName());

        // 启动allure报告服务（顶层套件）
        TestResultContainer testResultContainer = allureReportService.startTestContainer(testScenario);
        // 生命周期开始
        Allure.getLifecycle().startTestContainer(testResultContainer);

        // 2.获取该场景下的所有步骤（可以复用已有的列表查询）
        TestStep testStep = new TestStep();
        testStep.setScenarioId(scenarioId);
        List<TestStep> testStepList = testStepService.selectTestStepList(testStep);
        log.info("步骤数量：{}", testStepList.size());

        // 3.获取该场景下的所有测试参数
        TestScenarioData testScenarioData = new TestScenarioData();
        testScenarioData.setScenarioId(scenarioId);
        List<TestScenarioData> testScenarioDataList = testScenarioDataService.selectTestScenarioDataList(testScenarioData);
        log.info("参数数量：{}", testScenarioDataList.size());

        // 4.遍历参数列表，表示要执行几次该场景（有几套参数就表示要执行几次该场景）
        for (TestScenarioData scenarioData : testScenarioDataList) {
            log.info("------------------------------{} 参数场景开始执行------------------------------", scenarioData.getDataName());

            // 创建allure测试用例（创建测试结果对象（初始化））
            TestResult testResult = allureReportService.getTestResultInit(testScenario, scenarioData, testResultContainer);

            // 5. 获取场景参数信息，作为当前测试场景的参数上下文
            String parameters = scenarioData.getParameters();
            // 需要把json转成Map的形式
            @SuppressWarnings("unchecked")
            HashMap<String, Object> context = JSONUtil.parseObj(parameters).toBean(HashMap.class);
            log.info("参数上下文：{}", context);

            // Allure步骤记录参数信息
            Allure.step("参数信息", () -> {
                // 这个步骤有份参数附件
                Allure.addAttachment("测试参数", "application/json",
                        JSONUtil.toJsonPrettyStr(parameters));
            });

            boolean testCaseSuccess = true;
            // 6. 遍历当前数据当前场景的步骤列表，执行步骤
            for (TestStep step : testStepList) {
                log.info("步骤名称：{}", step.getName());

                boolean stepResult = executeTestStep(step, context);

                // Allure记录步骤结果需要（只要有一个步骤炸了，当前场景测试就标记为不通过）
                testCaseSuccess = testCaseSuccess && stepResult;

                if (!stepResult && step.getBreakOnFailure().equals(UserConstants.YES)) {
                    log.info("当前步骤执行失败，数据要求立刻结束当前数据场景的后续测试步骤");
                    break;
                }
            }

            // Allure设置测试场景结果（场景有多个步骤，只要有一个步骤炸了，当前场景测试就标记为不通过）
            testResult.setStatus(testCaseSuccess ? Status.PASSED : Status.FAILED);
            testResult.setStop(System.currentTimeMillis());
            // 结束当前测试用例（场景）的生命周期
            Allure.getLifecycle().stopTestCase(testResult.getUuid());
            Allure.getLifecycle().writeTestCase(testResult.getUuid());
            log.info("------------------------------{} 本套参数场景执行完毕------------------------------", scenarioData.getDataName());

        }

        // Allure停止顶层套件生命周期
        Allure.getLifecycle().stopTestContainer(testResultContainer.getUuid());
        Allure.getLifecycle().writeTestContainer(testResultContainer.getUuid());

        // TODO 7.整理测试结果，生成一份可以访问的allure报告(并清理之前的测试结果记录)，这里先用场景ID来生成
        boolean generateHtmlReport = allureReportService.generateHtmlReport(scenarioId);
        if (!generateHtmlReport){
            throw new ServiceException("生成allure报告失败");
        }

        // 8.业务结果恒为true，因为具体测试流程必须走下来，测试场景有失败的是正常现象，人allure分析即可
        return true;
    }


    /**
     * 执行测试步骤
     *
     * @param testStep 测试步骤
     * @param context  参数上下文
     * @return 步骤执行结果 true:成功 false:失败
     */
    private boolean executeTestStep(TestStep testStep, HashMap<String, Object> context) {
        try {
            // 1. 获取接口定义
            ApiDefinition apiDef = apiDefinitionMapper.selectApiDefinitionById(testStep.getApiId());

            // TODO 不能把jsonschema文件定义在接口表里，因为不同的业务参数，响应的结果是不同的，所以只能把jsonschema绑定到步骤表里
            // 获取json schema文件
            String jsonSchemaFile = testStep.getJsonSchemaFile();
            // 如果有就加入参数上下文（后续自定义断言需要用）
            if (jsonSchemaFile != null && !jsonSchemaFile.trim().isEmpty()) {
                context.put("jsonSchemaFile", jsonSchemaFile);
            }

            // 2.查询步骤要执行的断言组件
            String assertionRules = testStep.getAssertionRules();
            // 需要把json转成list
            List<String> componentNames = new ArrayList<>();
            if (assertionRules != null && !assertionRules.trim().isEmpty()) {
                try {
                    componentNames = new ObjectMapper().readValue(
                            assertionRules,
                            new TypeReference<List<String>>() {}
                    );
                } catch (Exception e) {
                    System.out.println("断言组件名称列表解析失败: " + e.getMessage());
                }
            }
            log.info("步骤要执行的断言组件名称列表：{}", componentNames);

            // 3. 渲染请求参数（替换变量url,header,body均支持${}替换）
            // url替换就可以很好的适配get请求，比如（/api/users?name=${username}&age=${age}）
            String actualUrl = SimpleTemplateUtils.render(apiDef.getUrl(), context);
            String actualHeaders = SimpleTemplateUtils.render(apiDef.getHeaders(), context);
            String actualBody = SimpleTemplateUtils.render(apiDef.getRequestTemplate(), context);

            // 4. 解析请求头
            Map<String, String> headersMap = parseHeaders(actualHeaders);

            // 5. 准备发送HTTP请求
            RequestSpecification request = RestAssured
                    .given()
                    .log().all();

            // 添加请求头
            for (Map.Entry<String, String> header : headersMap.entrySet()) {
                request.header(header.getKey(), header.getValue());
            }

            // 添加请求体（如果不是GET请求且body不为空）
            // 针对multipart/form-data的特殊处理
            if ("multipart/form-data".equals(headersMap.get("Content-Type"))) {
                // 解析body中的JSON，提取字段名和值
                Map<String, Object> formData = new ObjectMapper().readValue(actualBody, new TypeReference<Map<String, Object>>() {});
                for (Map.Entry<String, Object> entry : formData.entrySet()) {
                    request.multiPart(entry.getKey(), entry.getValue().toString());
                }
            } else {
                // 添加请求体（如果不是GET请求且body不为空）
                if (!HttpEnum.GET.getCode().equals(apiDef.getMethod()) && !actualBody.trim().isEmpty()) {
                    request.body(actualBody);
                }
            }

            // 6. 发送HTTP请求
            Response response = request.when()
                    .request(HttpEnum.getMethodByCode(apiDef.getMethod()), actualUrl)
                    .then()
                    .extract().response();

            log.info("响应结果: {}", response.asString());

            // Allure步骤记录响应信息
            allureReportService.recordHttpRequestResponse(
                    testStep,
                    apiDef,
                    headersMap,
                    actualUrl,
                    actualBody,
                    response
            );

            // 7. 提取数据到上下文
            extractDataToContext(response, testStep.getExtractRules(), context);

            // 8. 自定义断言验证
            // 如果断言组件名称列表不为空，则执行断言
            if (!componentNames.isEmpty()) {
                List<String> finalComponentNames = componentNames;
                // TODO allure步骤记录断言信息(补一下断言描述？代码？)
                Allure.step("执行断言组件：" + componentNames, assertionStep -> {

                    // 执行自定义断言
                    AssertionResult assertionResult = performAssertions(response, finalComponentNames, context);

                    if (assertionResult.isSuccess()) {
                        assertionStep.parameter("结果", "通过");
                        Allure.addAttachment("断言详情", "text/plain",
                                assertionResult.getMessage());
                    } else {
                        assertionStep.parameter("结果", "失败");
                        Allure.addAttachment("断言失败详情", "text/plain",
                                assertionResult.getMessage());
                        throw new AssertionError(assertionResult.getMessage());
                    }

                });
            }
            log.info("当前步骤测试通过");
            return true;

        } catch (AssertionError e) {
            // 断言失败处理
            log.info("框架断言失败: {}", e.getMessage());
            return false;
        } catch (Exception e) {
            log.info("步骤执行异常: {}", e.getMessage());
            return false;
        }
    }

    /**
     * TODO 解析JSON格式的请求头(为了节省配置工作量，建议这里直接改成附加请求头即可)
     */
    private Map<String, String> parseHeaders(String headersJson) {
        if (headersJson == null || headersJson.trim().isEmpty()) {
            return new HashMap<>();
        }
        try {
            return new ObjectMapper().readValue(headersJson,
                    new TypeReference<Map<String, String>>() {});
        } catch (Exception e) {
            System.out.println("请求头解析失败，使用默认Content-Type");
            Map<String, String> defaultHeaders = new HashMap<>();
            defaultHeaders.put("Content-Type", "application/json");
            defaultHeaders.put("Accept", "application/json");
            defaultHeaders.put("User-Agent", "KookAPI-TestClient/1.0");
            return defaultHeaders;
        }
    }

    /**
     * 从响应中提取数据到上下文
     */
    private void extractDataToContext(Response response, String extractRulesJson, Map<String, Object> context) {
        if (extractRulesJson == null || extractRulesJson.trim().isEmpty()) return;

        try {
            // 解析提取规则：[{"jsonPath":"$.token", "varName":"authToken"}]
            List<Map<String, String>> rules = new ObjectMapper().readValue(
                    extractRulesJson, new TypeReference<List<Map<String, String>>>() {});

            String responseBody = response.getBody().asString();

            for (Map<String, String> rule : rules) {
                String jsonPath = rule.get("jsonPath");
                String varName = rule.get("varName");

                if (jsonPath != null && varName != null) {
                    String value = JsonUtils.extractByJsonPath(responseBody, jsonPath);
                    if (value != null) {
                        context.put(varName, value);
                        log.info("提取变量: {} = {}", varName, value);
                    }
                }
            }
        } catch (Exception e) {
            log.info("提取数据失败: {}", e.getMessage());
        }
    }

    /**
     * 执行自定义断言（目前的逻辑是当一个场景某个步骤出错后，后续步骤就不再执行了，但步骤的断言是会都走完的）
     * @param response 接口响应对象
     * @param componentNames 断言组件名称列表
     * @param context 测试上下文（这里可以用来在上下文中放一些提取到的参数，从而进行自定义校验）
     * @return 断言结果对象
     */
    private AssertionResult performAssertions(Response response, List<String> componentNames, Map<String, Object> context) {
        List<AssertionResult> results = new ArrayList<>();

        for (String componentName : componentNames) {
            // 根据组件名称获取断言组件
            AssertionComponent component = assertionComponentRegistry.getComponent(componentName);
            if (component == null) {
                String errorMsg = "未找到断言组件: " + componentName;
                results.add(AssertionResult.failure(errorMsg));
                continue;
            }

            // 执行自定义断言并保存结果
            AssertionResult result = component.execute(response, context);
            results.add(result);
        }

        // 构建详细的断言结果信息
        StringBuilder messageBuilder = new StringBuilder();
        messageBuilder.append("断言执行汇总:\n");

        long passedCount = results.stream().filter(AssertionResult::isSuccess).count();
        long failedCount = results.size() - passedCount;

        messageBuilder.append("总计: ").append(results.size())
                .append(", 通过: ").append(passedCount)
                .append(", 失败: ").append(failedCount).append("\n\n");

        // 按执行顺序添加每个断言的详细结果
        for (int i = 0; i < componentNames.size(); i++) {
            String componentName = componentNames.get(i);
            AssertionResult result = results.get(i);

            messageBuilder.append(i + 1).append(". ").append(componentName).append(": ")
                    .append(result.isSuccess() ? "✅ 通过" : "❌ 失败").append("\n");

            // 添加具体的断言信息（保留原始的详细信息）
            String detailMessage = result.getMessage();
            if (detailMessage != null && !detailMessage.isEmpty()) {
                // 对详细信息进行缩进处理
                String indentedDetails = detailMessage.replaceAll("(?m)^", "    ");
                messageBuilder.append(indentedDetails).append("\n");
            }
            messageBuilder.append("\n");
        }

        // 如果所有断言都通过
        boolean allPassed = failedCount == 0;
        String finalMessage = messageBuilder.toString().trim();

        return allPassed ?
                AssertionResult.success(finalMessage) :
                AssertionResult.failure(finalMessage);
    }

}
